import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.geom.Point2D;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;

import javax.swing.JComponent;
import javax.swing.SwingUtilities;


@SuppressWarnings("serial")
public class Clock
extends JComponent
implements Runnable
{
    private double spirals          = 0.9;
    private double radianHourStep   = Math.PI * 2.0 / 12;
    private double radianMinuteStep = Math.PI * 2.0 / 60;
    private int    hours24;
    private int    minutes;
    private int    seconds;
    private double hours24Decimal;
    private double minutesDecimal;
    private double secondsDecimal;
    
    @Override
    public void paint(Graphics graphics)
    {
        Graphics2D g2d = (Graphics2D) graphics;
        
        // variable
        updateHMS();
        
        // draw background
        int diameter = drawBackground(g2d);
        
        // draw spiral
        g2d.setColor(Color.DARK_GRAY);
        int xCenter = getWidth() / 2;
        int yCenter = getHeight() / 2;
        
        int radius = diameter / 2;
        
        // draw spiral
        double turns = 10; // do 10 turns
        Color[] colors = new Color[(int) (turns + 1)];
        for (int i = 0; i < turns + 1; ++i)
        {
            if (i % 2 == 0)
            {
                colors[i] = new Color(0, 70, 174);
            }
            else
            {
                colors[i] = new Color(0, 45, 111);
            }
        }
        drawSpiral(g2d, xCenter, yCenter, turns, radius, colors);
        
        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        
        // drawSymbols(g2d, xCenter, yCenter, turns, radius);
        
        // draw read line
        // g2d.setStroke(new BasicStroke(1.0f));
        // g2d.drawLine(xCenter, yCenter, xCenter, 0);
        
        // draw figures
        
        // hours
        double hoursAngle = (-hours24Decimal - 24) * radianHourStep;
        for (int i = 0; i < (int) turns * 24; ++i)
        {
            Point2D p = getPointForAngle(hoursAngle, 0.3);
            float x = (float) (xCenter + p.getX() * radius);
            float y = (float) (yCenter + p.getY() * radius);
            
            int fontSize = (int) (radius / 5);
            if (i - 24 == hours24)
            {
                fontSize = (int) (radius / 3);
                g2d.setColor(Color.WHITE);
            }
            else
            {
                if (i % 12 == i % 24) // AM
                {
                    g2d.setColor(new Color(170, 204, 255));
                }
                else
                // PM
                {
                    g2d.setColor(new Color(0, 0, 0));
                }
            }
            
            Font font = new Font("Anatolian", Font.ITALIC, (int) resizeByAngle(fontSize, hoursAngle));
            
            String hourString = "" + (i % 24);
            FontMetrics fontMetrics = g2d.getFontMetrics(font);
            int yDiff = -fontMetrics.getAscent() / 2 + fontMetrics.getDescent() / 2;
            int xDiff = (int) (fontMetrics.getStringBounds(hourString, g2d).getWidth() / 2);
            
            g2d.setFont(font);
            g2d.drawString(hourString, x - xDiff, y - yDiff);
            
            hoursAngle += radianHourStep;
        }
        
        // minutes
        double minutesAngle = (-minutesDecimal - 60) * radianMinuteStep;
        for (int i = 0; i < (int) turns * 60; ++i)
        {
            if (i % 5 == 0)
            {
                if (Math.abs((i % 60) - minutes) < 3 && i / 60 == 1)
                {
                    double relativeSpiralPosition = 0.6;
                    drawNumber(g2d, xCenter, yCenter, radius, relativeSpiralPosition, minutesAngle, "" + (i % 60), Color.YELLOW, radius / 8);
                }
                else
                {
                    double relativeSpiralPosition = 0.6;
                    drawNumber(g2d, xCenter, yCenter, radius, relativeSpiralPosition, minutesAngle, "" + (i % 60), Color.GRAY, radius / 8);
                }
            }
            
            minutesAngle += radianMinuteStep;
        }
        
        // draw hands
        g2d.setStroke(new BasicStroke(1.0f));
        
        // seconds
        // g2d.setColor(Color.YELLOW);
        // double secondsAngle = secondsDecimal * radianMinuteStep - minutesDecimal * radianMinuteStep;
        // for (int i = 0; i < (int) turns; ++i)
        // {
        // double position = 0.9;
        // Point2D p = getPointForAngle(secondsAngle, position);
        // float x = (float) (xCenter + p.getX() * radius);
        // float y = (float) (yCenter + p.getY() * radius);
        //
        // int fontSize = radius / 10;
        // Font font = new Font("Anatolian", Font.ITALIC, (int) resizeByAngle(fontSize, secondsAngle));
        // g2d.setFont(font);
        //
        // String hourString = "" + seconds;
        // FontMetrics fontMetrics = g2d.getFontMetrics(font);
        // int yDiff = -fontMetrics.getAscent() / 2 + fontMetrics.getDescent() / 2;
        // int xDiff = (int) (fontMetrics.getStringBounds(hourString, g2d).getWidth() / 2);
        //
        // g2d.drawString(hourString, x - xDiff, y - yDiff);
        //
        // secondsAngle += radianMinuteStep * 60;
        // }
        
        // seconds
        double secondesAngle = (-secondsDecimal - 120) * radianMinuteStep;
        for (int i = 0; i < (int) turns * 60; ++i)
        {
            double relativeSpiralPosition = 0.8;
            if (i % 2 == 0)
            {
                if (Math.abs((i % 60) - seconds) < 2 && i / 60 == 2)
                {
                    drawNumber(g2d, xCenter, yCenter, radius, relativeSpiralPosition, secondesAngle, "" + (i % 60), Color.CYAN, radius / 15);
                }
                else
                {
                    drawNumber(g2d, xCenter, yCenter, radius, relativeSpiralPosition, secondesAngle, "" + (i % 60), Color.LIGHT_GRAY, radius / 15);
                }
            }
            
            secondesAngle += radianMinuteStep;
        }
        
        // debug
        g2d.setFont(new Font("Monospaced", Font.BOLD, 16));
        g2d.drawString(new Date().toString(), 20, 20);
    }
    
    private void drawNumber(Graphics2D g2d, int xCenter, int yCenter, int radius, double position, double angle, String value, Color color, int fontSize)
    {
        g2d.setColor(color);
        
        Point2D p = getPointForAngle(angle, position);
        float x = (float) (xCenter + p.getX() * radius);
        float y = (float) (yCenter + p.getY() * radius);
        
        Font font = new Font("Anatolian", Font.BOLD, (int) resizeByAngle((int) resizeByAngle(fontSize, angle), 0));
        g2d.setFont(font);
        
        FontMetrics fontMetrics = g2d.getFontMetrics(font);
        int yDiff = -fontMetrics.getAscent() / 2 + fontMetrics.getDescent() / 2;
        int xDiff = (int) (fontMetrics.getStringBounds(value, g2d).getWidth() / 2);
        
        g2d.drawString(value, x - xDiff, y - yDiff);
    }
    
    private void updateHMS()
    {
        GregorianCalendar gregorianCalendar = new GregorianCalendar();
        
        hours24 = gregorianCalendar.get(Calendar.HOUR_OF_DAY);
        minutes = gregorianCalendar.get(Calendar.MINUTE);
        seconds = gregorianCalendar.get(Calendar.SECOND);
        
        secondsDecimal = seconds + gregorianCalendar.get(Calendar.MILLISECOND) / 1000.0;
        minutesDecimal = minutes + secondsDecimal / 60.0;
        hours24Decimal = hours24 + minutesDecimal / 60.0;
    }
    
    // changer la couleur en fonction du jour
    private void drawSpiral(Graphics2D g2d, int xCenter, int yCenter, double turns, int radius, Color[] colors)
    {
        double dayAngle = -hours24Decimal * radianHourStep;
        double drawingStep = radianHourStep / 10;
        int colorIndex = 0;
        
        for (int i = 0; i < turns * 12 * 10; ++i)
        {
            // draw line
            Point2D p1 = getPointForAngle(dayAngle);
            
            int x1 = (int) (xCenter + p1.getX() * radius);
            int y1 = (int) (yCenter + p1.getY() * radius);
            
            Point2D p2 = getPointForAngle(dayAngle + drawingStep);
            
            int x2 = (int) (xCenter + p2.getX() * radius);
            int y2 = (int) (yCenter + p2.getY() * radius);
            
            // System.out.println("x1=" + x1 + " y1=" + y1 + " x2=" + x2 + " y2=" + y2 + " l=" + currentLength + " a=" + currentAngle);
            g2d.setStroke(new BasicStroke((float) resizeByAngle(radius / 125, dayAngle)));
            
            int nPoints = 3;
            int[] xPoints = new int[nPoints];
            int[] yPoints = new int[nPoints];
            
            xPoints[0] = xCenter;
            xPoints[1] = x1;
            xPoints[2] = x2;
            
            yPoints[0] = yCenter;
            yPoints[1] = y1;
            yPoints[2] = y2;
            
            if (i % 120 == 0)
            {
                colorIndex++;
                g2d.setColor(colors[colorIndex]);
            }
            
            g2d.fillPolygon(xPoints, yPoints, nPoints);
            // g2d.drawLine(x1, y1, x2, y2);
            
            dayAngle += drawingStep;
        }
    }
    
    private void drawSymbols(Graphics2D g2d, int xCenter, int yCenter, double turns, int radius)
    {
        for (int i = 0; i < turns * 60; ++i)
        {
            // draw line
            double currentAngle = radianMinuteStep * i;
            Point2D p1 = getPointForAngle(currentAngle, 0.0);
            
            double ratio = 0;
            if (i % 10 == 0)
            {
                ratio = 0.9;
            }
            else
            {
                ratio = 1.0;
            }
            
            int x1 = (int) (xCenter + p1.getX() * radius);
            int y1 = (int) (yCenter + p1.getY() * radius);
            int x2 = (int) (xCenter + p1.getX() * radius * ratio);
            int y2 = (int) (yCenter + p1.getY() * radius * ratio);
            
            g2d.setStroke(new BasicStroke((float) resizeByAngle(radius / 50, currentAngle)));
            g2d.drawLine(x1, y1, x2, y2);
        }
    }
    
    /**
     * 
     * @param currentAngle
     *            the angle
     * @param location
     *            0=on the external spiral, 1=on the internal spiral
     * @return a point
     */
    private Point2D getPointForAngle(double currentAngle, double location)
    {
        Point2D p1 = getPointForAngle(currentAngle);
        Point2D p2 = getPointForAngle(currentAngle + Math.PI * 2);
        
        double p1Weight = 1 - location;
        double p2Weight = location;
        
        return new Point2D.Double(p1.getX() * p1Weight + p2.getX() * p2Weight, p1.getY() * p1Weight + p2.getY() * p2Weight);
    }
    
    private Point2D.Double getPointForAngle(double currentAngle)
    {
        double x = Math.sin(currentAngle) * resizeByAngle(1.0, currentAngle);
        double y = -Math.cos(currentAngle) * resizeByAngle(1.0, currentAngle);
        
        return new Point2D.Double(x, y);
    }
    
    private double resizeByAngle(double initialSize, double currentAngle)
    {
        return initialSize * Math.pow(spirals, currentAngle);
    }
    
    private int drawBackground(Graphics2D g2d)
    {
        g2d.setColor(Color.BLACK);
        g2d.fillRect(0, 0, getWidth(), getHeight());
        int diameter = Math.min(getWidth(), getHeight());
        int xStart = 0;
        int yStart = 0;
        
        if (getWidth() > getHeight())
        {
            xStart = (getWidth() - diameter) / 2;
        }
        else
        {
            yStart = (getHeight() - diameter) / 2;
        }
        
        g2d.setColor(Color.WHITE);
        g2d.drawOval(xStart, yStart, diameter, diameter);
        return diameter;
    }
    
    @Override
    public void run()
    {
        while (true)
        {
            try
            {
                SwingUtilities.invokeAndWait(new Runnable()
                {
                    
                    @Override
                    public void run()
                    {
                        repaint();
                    }
                });
                
                int sleepTime = (int) (50 - System.currentTimeMillis() % 50);
                // System.out.println("Sleep time " + sleepTime);
                Thread.sleep(sleepTime);
            }
            catch (Exception e)
            {
                e.printStackTrace();
            }
        }
    }
    
    public double getSpirals()
    {
        return spirals;
    }
    
    public void setSpirals(double spirals)
    {
        this.spirals = spirals;
    }
    
    public double getRadianHourStep()
    {
        return radianHourStep;
    }
    
    public void setRadianHourStep(double radianHourStep)
    {
        this.radianHourStep = radianHourStep;
    }
    
    public double getRadianMinuteStep()
    {
        return radianMinuteStep;
    }
    
    public void setRadianMinuteStep(double radianMinuteStep)
    {
        this.radianMinuteStep = radianMinuteStep;
    }
    
}
